package ms.unicorn.modellanguage;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ms.unicorn.Element;
import ms.unicorn.IPropertyDescriptorsProvider;
import ms.unicorn.language.languagedef.ElementDef;
import ms.unicorn.language.languagedef.LanguageDef;
import ms.unicorn.language.languagedef.Module;
import ms.unicorn.language.languagedef.ViewDefUtil;
import ms.unicorn.language.ui.internal.UnicornLanguageDefinitionActivator;
import ms.unicorn.util.ErrorHandler;
import ms.unicorn.util.MLExtensionSchema;
import ms.unicorn.util.PropertyDescriptorProvider;

import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.ResourceLocator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.Resource.Diagnostic;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.edit.provider.ComposeableAdapterFactory;
import org.eclipse.emf.edit.provider.IItemPropertyDescriptor;

import com.google.inject.Injector;

public class LanguageRegistry implements MLExtensionSchema, IPropertyDescriptorsProvider {
		
	private Map<String, IModelLanguage> languages = new HashMap<String, IModelLanguage>();
	private boolean init = false;
	
	private static ResourceSet rsLangDef = new ResourceSetImpl();
	private static Map<String, LanguageElement> elementMap;
	private static Map<ElementDef, LanguageElement> elementDefMap = new HashMap<ElementDef, LanguageElement>();
	public static final LanguageRegistry INSTANCE = new LanguageRegistry();

	private LanguageRegistry() {
		//registerLanguage(new DefaultLanguage());
		//initialize();
		elementMap = new HashMap<String, LanguageElement>();
		PropertyDescriptorProvider.INSTANCE = this;
	}
	
	protected void initialize(boolean reinit) {
		if (!init || reinit) {
			init = true;
			languages.clear();
			ViewDefUtil.clearParserCache();
			rsLangDef = new ResourceSetImpl();
			IExtensionRegistry registry = Platform.getExtensionRegistry();
			IConfigurationElement[] configs = registry.getConfigurationElementsFor(EXTENSION_ID);
	
			for (IConfigurationElement item : configs) {
				if (MODEL_LANGUAGE.equalsIgnoreCase(item.getName())) {
					createModelLanguage(item);
				} 
			}
	
			registerElementTypes();
		}
	}

	private void createModelLanguage(IConfigurationElement item) {
		try {
			IModelLanguage lang = (IModelLanguage) item.createExecutableExtension("handler");
			Injector injector = UnicornLanguageDefinitionActivator.getInstance().getInjector("ms.unicorn.language.UnicornLanguageDefinition");
			injector.injectMembers(lang);
			lang.initialize(item);
			registerLanguage(lang);

		} catch (Exception e) {
			ErrorHandler.handleException(e);
		}
	}

	public Collection<IModelLanguage> getLanguages(boolean reinit) {
		initialize(reinit);
		return languages.values();
	}
	
	public LanguageElement getLanguageElement(String id) {
		return elementMap.get(id);
	}

	public LanguageElement getLanguageElement(Element domainElement) {
		LanguageElement e = null;
		if (domainElement != null) {
			String typeId = domainElement.getElementType();
			e = elementMap.get(typeId);
			/*for(IModelLanguage lang : getLanguages()) {
				e = lang.getElement(typeId);
				if (e != null)
					break;
			}*/
		}
		return e;
	}
	
	public LanguageElement getLanguageElement(ElementDef def) {
		return elementDefMap.get(def);
	}

	public void registerLanguage(IModelLanguage language) {
		initialize(false);
		languages.put(language.getId(), language);
	}
	
	public void registerLanguageElement(LanguageElement element) {
		elementMap.put(element.getId(), element);
		elementDefMap.put(element.getDefinition(), element);
	}

	public static LanguageDef loadLanguageDefinition(URI langDefFile) {
		Resource resource = rsLangDef.getResource(langDefFile, true);
		EList<Diagnostic> errors = resource.getErrors();
		
		if (errors.size() > 0)
			throw new RuntimeException(String.format("Error loading language definition file: %s\nMessage:%s", 
					langDefFile.toString(), errors.get(0).toString()));
		
		EList<Diagnostic> warnings = resource.getWarnings();
		if (warnings.size() > 0)
			for(Diagnostic warning: warnings)
				System.out.println(warning.getMessage());
		
		if (resource.getContents().size() > 0) {
			Module m = (Module) resource.getContents().get(0);
			if (m instanceof LanguageDef)
				return (LanguageDef) m;
		}
		throw new RuntimeException("Invalid language definition file: " + langDefFile.toString());
			
	}
	
	public void removeElement(String elementId) {
		initialize(false);
		if (languages.containsKey(elementId))
			languages.remove(elementId);
	}

	public IModelLanguage getLanguage(String language) {
		initialize(false);
		return languages.get(language);
	}

	protected void registerElementTypes() {
		for(IModelLanguage lang: languages.values())
			lang.registerElementTypes();
	}

	@Override
	public List<IItemPropertyDescriptor> getPropertyDescriptors(
			ComposeableAdapterFactory composeableAdapterFactory,
			ResourceLocator resourceLocator, Element object,
			List<IItemPropertyDescriptor> list) {
		
		LanguageElement le = getLanguageElement(object);
		if (le != null)
			return le.getPropertyDescriptors(composeableAdapterFactory, resourceLocator, list);
		return list;
	}
}
